
#ifndef Velyn_Smooth_h
#define Velyn_Smooth_h

namespace Velyn {
    
    namespace Subdivisions {

        class Smooth {
            
        private:
            
            // map midpoints to existing vertices in the mesh
            map<int, VertexIndex> midpointIndices;
            
            Texture calculateMidpoint(Texture texture0, Texture texture1) {
                return (texture0 + texture1) * 0.5;
            }
            
            // hash the vertex indicies
            int getHashKey(int point0, int point1, int hashMultiplier) {
                int key;
                
                if(point0 < point1) {
                    key = hashMultiplier * point0 + point1;
                } else {
                    key = hashMultiplier * point1 + point0;
                }
                
                return key;
            }
            
        public:
            
            Smooth() {}
            
            ~Smooth() {}

            TriangleMesh* smoothSurface(TriangleMesh* mesh) {
                
                // copy over the vertices, normals, and textures
                TriangleMesh* newMesh = new TriangleMesh(*mesh);
                
                // clear the map
                midpointIndices.clear();
                
                // for each triangle
                for(int triangle = 0; triangle < mesh->numberTriangles(); triangle++) {
                    
                    // get the vertices for the triangle
                    VertexList vertices = mesh->getVertices(triangle);
                    
                    // get the indicies of the vertex indices for the triangle
                    VertexIndexList vertexIndex = mesh->getVertexIndices(triangle);
                    
                    // get the normals for the triangle
                    NormalList normals = mesh->getNormals(triangle);
                    
                    // get the textures
                    TextureList textures = mesh->getTextures(triangle);
                    
                    int hashKey;
                    int midpointIndex[3];
                    
                    // NormalList normals = mesh->calculateNormals(triangle);
                    
                    map<int, int>::const_iterator findIterator;
                    
                    // hash the set of vertices and see if they exist in the map
                    // if they do then use the index in the map
                    // if they do not exist then create the midpoint and map it to that index
                    
                    // for midpoint 1
                    // get the hash key
                    hashKey = getHashKey(vertexIndex[0], vertexIndex[1], mesh->numberVertices());
                    
                    // check if it is in the map
                    findIterator = midpointIndices.find(hashKey);
                    
                    // if the key does not exist
                    if(findIterator == midpointIndices.end()) {
                        Vertex midpoint;
                        Normal normal;
                        Texture texture;
                        
                        float fU = 0.0f;
                        float fV = 0.5f;
                        float fW = 0.5f;
                        
                        Vector3 projection[3];
        
                        Vector3 fNormals[3];
                        
                        fNormals[0] = normals[0];
                        fNormals[1] = normals[1];
                        fNormals[2] = normals[2];
                        
                        fNormals[0].negate();
                        fNormals[1].negate();
                        fNormals[2].negate();
                        
                        // terrain generation create terrain packages and render 
                        // bryce 256x256 png at 45 degeers of height maps
                        
                        midpoint = (vertices[0] * fW) + (vertices[1] * fV) + (vertices[2] * fU);
                        
                        projection[0] = midpoint - (midpoint-vertices[0]).dot(normals[0]) * normals[0];
                        projection[1] = midpoint - (midpoint-vertices[1]).dot(normals[1]) * normals[1];
                        projection[2] = midpoint - (midpoint-vertices[2]).dot(normals[2]) * normals[2];
                        
                        midpoint = (projection[0] * fW) + (projection[1] * fV) + (projection[2] * fU);
                        
                        normal = (normals[0] * fW) + (normals[1] * fV) + (normals[2] * fU);
                        
                        midpointIndex[0] = newMesh->addVertex(midpoint);
                        
                        newMesh->addNormal(normal);
                        
                        // calculate the texture
                        texture = calculateMidpoint(textures[0], textures[1]);
                        
                        // add the new texture
                        newMesh->addTexture(texture);
                        
                        midpointIndices[hashKey] = midpointIndex[0];
                        
                    } else {
                        
                        // if the key does exist, get its index from the map
                        midpointIndex[0] = findIterator->second;
                    }
                    
                    // repeat for midpoint 2
                    hashKey = getHashKey(vertexIndex[1], vertexIndex[2], mesh->numberVertices());
                    findIterator = midpointIndices.find(hashKey);
                    
                    if(findIterator == midpointIndices.end()) {
                        Vertex midpoint;
                        Normal normal;
                        Texture texture;
                        
                        float fU = 0.5f;
                        float fV = 0.5f;
                        float fW = 0.0f;
                        
                        Vector3 projection[3];
                        
                        midpoint = (vertices[0] * fW) + (vertices[1] * fV) + (vertices[2] * fU);
                        
                        projection[0] = midpoint - (midpoint-vertices[0]).dot(normals[0]) * normals[0];
                        projection[1] = midpoint - (midpoint-vertices[1]).dot(normals[1]) * normals[1];
                        projection[2] = midpoint - (midpoint-vertices[2]).dot(normals[2]) * normals[2];
                        
                        midpoint = (projection[0] * fW) + (projection[1] * fV) + (projection[2] * fU);
                        
                        normal = (normals[0] * fW) + (normals[1] * fV) + (normals[2] * fU);
                        
                        midpointIndex[1] = newMesh->addVertex(midpoint);
                        
                        newMesh->addNormal(normal);
                        
                        texture = calculateMidpoint(textures[1], textures[2]);
                        newMesh->addTexture(texture);
                        
                        midpointIndices[hashKey] = midpointIndex[1];
                        
                    } else  {
                        midpointIndex[1] = findIterator->second;
                    }
                    
                    // and for midpoint 3
                    hashKey = getHashKey(vertexIndex[2], vertexIndex[0], mesh->numberVertices());
                    findIterator = midpointIndices.find(hashKey);
                    
                    if(findIterator == midpointIndices.end()) {
                        Vertex midpoint;
                        Normal normal;
                        Texture texture;
                        
                        float fU = 0.5f;
                        float fV = 0.0f;
                        float fW = 0.5f;
                        
                        Vector3 projection[3];
                        
                        midpoint = (vertices[0] * fW) + (vertices[1] * fV) + (vertices[2] * fU);
                        
                        projection[0] = midpoint - (midpoint-vertices[0]).dot(normals[0]) * normals[0];
                        projection[1] = midpoint - (midpoint-vertices[1]).dot(normals[1]) * normals[1];
                        projection[2] = midpoint - (midpoint-vertices[2]).dot(normals[2]) * normals[2];
                        
                        midpoint = (projection[0] * fW) + (projection[1] * fV) + (projection[2] * fU);
                        
                        normal = (normals[0] * fW) + (normals[1] * fV) + (normals[2] * fU);
                        
                        midpointIndex[2] = newMesh->addVertex(midpoint);
                        
                        newMesh->addNormal(normal);
                        
                        texture = calculateMidpoint(textures[2], textures[0]);
                        newMesh->addTexture(texture);
                        
                        midpointIndices[hashKey] = midpointIndex[2];
                        
                    } else {
                        midpointIndex[2] = findIterator->second;
                    }
                    
                    // FIXME: this is not mapping correctly
                    // add triangles to newMesh in clockwise order
                    newMesh->addTriangle(vertexIndex[0], midpointIndex[0], midpointIndex[2]);
                    newMesh->addTriangle(midpointIndex[0], vertexIndex[1], midpointIndex[1]);
                    newMesh->addTriangle(midpointIndex[0], midpointIndex[1], midpointIndex[2]);
                    newMesh->addTriangle(midpointIndex[2], midpointIndex[1], vertexIndex[2]);
                }
                
                // swap the addresses of mesh and newMesh
                // std::swap(mesh, newMesh);
                
                delete mesh;
                
                return newMesh;
            }
            
        };  
        
    }
    
}

#endif
